package net.crazyadam.activity;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import net.crazyadam.audio.Constant;

import java.util.HashSet;
import java.util.Set;

/**
 * Copyright by <a href="http://crazyadam.net"><em><i>Joseph J.C. Tang</i></em></a> <br/>
 * Email: <a href="mailto:jinntrance@gmail.com">jinntrance@gmail.com</a>
 *
 * @author joseph
 * @version 1.0
 * @since 10/18/12 5:09 PM
 */
public class AudioRecorder extends Thread {
    protected boolean receivable = true;
    private static int count=0;
    //private int inputBlockSize = 2048;
    private int inputBlockSize = (int) (PlaySound.codedDuration * PlaySound.sampleRate/2);
    private Set<Integer> decodedIds = new HashSet<>();

    @Override
    public void run() {
        int audioBuf = AudioRecord.getMinBufferSize(Constant.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

        // Set up the audio input.
        AudioRecord audioInput = new AudioRecord(MediaRecorder.AudioSource.MIC, Constant.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, audioBuf);
        audioInput.startRecording();
        int inputBufferIndex = 0;
        short[] buffer = new short[inputBlockSize];
//        FFTTransformer spectrumAnalyser = new FFTTransformer(inputBlockSize, Constant.windowFunc);

        // Allocate the spectrum data.
        float[] spectrumData = new float[inputBlockSize / 2];
        int historyLen = 4;
        float[][] spectrumHist = new float[inputBlockSize / 2][historyLen];
        int spectrumIndex = 0;

        boolean encoded = false;
        int skip=0;
        double frequencyStart=PlaySound.freqStart;
        while (receivable) {
            int readSize = inputBlockSize;
            int space = inputBlockSize - inputBufferIndex;
            if (readSize > space) readSize = space;
            int index = inputBufferIndex;
            int nread = audioInput.read(buffer, index, readSize);//read size
            if (!receivable) break;
            if (nread < 0) {
                receivable = false;
                break;
            }
            int end = inputBufferIndex + nread;
            if (end >= inputBlockSize) {
                inputBufferIndex = 0;
//                spectrumAnalyser.setInput(buffer, 0, inputBlockSize);
//                spectrumAnalyser.transform();
//                Get the FFT output.
//                if (historyLen <= 1)
//                    spectrumAnalyser.getResults(spectrumData);
//                else
//                    spectrumIndex = spectrumAnalyser.getResults(spectrumData,spectrumHist,spectrumIndex);
//                int freq18k = (int) Math.round(inputBlockSize * PlaySound.freqStart / Constant.SAMPLE_RATE)-1;
//                int freq22k = (int) Math.round(inputBlockSize * (PlaySound.freqStart+PlaySound.freqStep/2) / Constant.SAMPLE_RATE)-1;
//                int freq22k = (int) (Math.round(inputBlockSize * (16500.0) / Constant.SAMPLE_RATE) -1);
//                if(spectrumData[freq18k]>10*spectrumData[freq22k]){
//                if(spectrumData[freq18k]>1e-4){
//                if(spectrumData[freq18k]>1e-4){
//                    int code=decode(spectrumData);
//                    decodedIds.add(code);
//                }
                //if(complexDetect(buffer,PlaySound.freqStart)>Constant.complexThreshold){
                //    int code=decode(buffer);
                //    decodedIds.add(code);
                //}
                if(skip>0){
                    skip--;
                    buffer = new short[inputBlockSize];
                    continue;
                }
                if(false)
                    switch (deModulate(buffer, (int) frequencyStart, (int) ( frequencyStart + PlaySound.freqStep))) {
                    case 1:
                        System.out.println("decoded 1");
                        frequencyStart+=2*PlaySound.freqStep;
                        frequencyStart=PlaySound.freqStart+(frequencyStart-PlaySound.freqStart)%(2*PlaySound.length*PlaySound.freqStep);
//                        skip=1;
                        break;
                    case 0:
                        System.out.println("decoded 0");
                        frequencyStart+=2*PlaySound.freqStep;
                        frequencyStart=PlaySound.freqStart+(frequencyStart-PlaySound.freqStart)%(2*PlaySound.length*PlaySound.freqStep);
//                        skip=1;
                        break;
                    default:
//                        encoded=false;
                    }
//                  System.out.println("decoded to "+decode(buffer,PlaySound.length,PlaySound.freqStart));
                if(-1!=deModulate(buffer, (int) PlaySound.freqSync, (int) (PlaySound.freqSync+ PlaySound.freqStep))){
                   encoded=true;
//                    frequencyStart=PlaySound.freqStart;
                }
                deModulate(buffer, (int) frequencyStart, (int) ( frequencyStart + PlaySound.freqStep));
                deModulate(buffer, (int) (frequencyStart+2*PlaySound.freqStep), (int) ( frequencyStart + 3*PlaySound.freqStep));
//                deModulate(buffer, (int) PlaySound.freqStart, (int) (PlaySound.freqStart + PlaySound.freqStep));
//                {
//                    encoded=!encoded;
//                    skip=1;
//                }


                buffer = new short[inputBlockSize];
                count++;
            } else
                inputBufferIndex = end;
        }
        audioInput.stop();
    }

    private int decode(short[] buffer) {
        int code = 0;
        for (int i = 1; i <= PlaySound.length; i++, code <<= 1) {
            if (complexDetect(buffer, PlaySound.freqStart + i * PlaySound.freqStep) > Constant.complexThreshold)
                code = code | 1;
        }
        return code;
    }
    private int decode(short[] buffer,int length,double frequencyStart){
        int code=0;
      for(int i=0;i<length;i++){
          int r=deModulate(buffer, (int)( frequencyStart+i*PlaySound.freqStep), (int) ( frequencyStart + (i+1)*PlaySound.freqStep));
          if(-1!=r) code+=r*2^i;
      }
        return code;
    }

    /**
     * @param spectrumData
     * @see net.crazyadam.activity.PlaySound#genWave(double, int, int, int)
     */
    private int decode(float[] spectrumData) {
        int code = 0;
        for (int i = 1; i <= PlaySound.length; i++, code <<= 1) {
            int freqIndex = (int) Math.round(inputBlockSize * (PlaySound.freqStart + i * PlaySound.freqStep) / Constant.SAMPLE_RATE) - 1;
            int minIndex = (int) Math.round(inputBlockSize * (PlaySound.freqStart + i * PlaySound.freqStep + PlaySound.freqStep / 2) / Constant.SAMPLE_RATE) - 1;
//            int minIndex = (int) (Math.round(inputBlockSize * (16500.0) / Constant.SAMPLE_RATE) -1);
//            if (spectrumData[freqIndex]>=10*spectrumData[minIndex])
            if (spectrumData[freqIndex] >= 1e-4)
                code = code | 1;
        }
        code >>= 1;//the last loop shifted one more bit
        return code;
    }

    public void setBlockSize(int inputBlockSize) {
        this.inputBlockSize = inputBlockSize;
    }

    public void stopRecorder() {
        this.receivable = false;
    }

    public Set<Integer> getCodes() {
        return decodedIds;
    }

    /**
     * 检测信号中是否含有某个频段的信号
     *
     * @param signal    信号序列
     * @param frequency 待检测的频率
     * @return 相关度，大于{@link Constant#complexThreshold } </a>则表示内含此信号。
     * @see Constant#complexThreshold
     */
    private static double complexDetect(short[] signal, double frequency) {
        double realSum = 0;
        double imaginarySum = 0;
        double u = 2 * Math.PI * frequency / Constant.SAMPLE_RATE;
        // y = e^(ju) = cos(u) + j * sin(u)

        for (int i = 0; i < signal.length; i++) {
            //System.out.println("signal[" +i +"]: " +signal[i] + "; convert: " + (signal[i])/(float)Constants.kFloatToByteShift);
            realSum += (Math.cos(i * u) * (signal[i] / (float) Constant.kFloatToByteShift));
            imaginarySum += (Math.sin(i * u) * (signal[i] / (float) Constant.kFloatToByteShift));
        }
        //System.out.println("realSum=" + realSum + "; imSum=" + imaginarySum);
        double realAve = realSum / signal.length;
        double imaginaryAve = imaginarySum / signal.length;
//       	System.out.println("u:" + u + " realAve:" + realAve + " imaginaryAve:" + imaginaryAve
//       			   + " \r\nfrequency:" + frequency + " signal.length:" + signal.length
//       			   + " realSum:" + realSum + " imaginarySum:" + imaginarySum
//       			   + "signal[100]:" + (signal[100]/(float)Constants.kFloatToByteShift));
        // return the abs ( realAve + imaginaryAve * i ) which equals sqrt( realAve^2 + imaginaryAve^2)
        return Math.sqrt((realAve * realAve) + (imaginaryAve * imaginaryAve));
    }

    /**
     * @param receivedSignal 数字信号序列
     * @param freqOne        调制为1的频段
     * @param freqZero       调制为0的频段
     * @return 1或者0
     */
    public int deModulate(short[] receivedSignal, int freqOne, int freqZero) {
        double sumOne=0,sumZero=0;
        int index = 0,result=-1;
        for (short s : receivedSignal) {
            sumOne += s * Math.sin(2 * Math.PI * freqOne * index / Constant.SAMPLE_RATE);
            sumZero += s* Math.sin(2 * Math.PI * freqZero * index / Constant.SAMPLE_RATE);
            index++;
        }
        double avgOne=sumOne/index,avgZero=sumZero/index;
        System.out.println(freqOne+"sum" + (Math.abs(sumOne)-Math.abs(sumZero)));
/*        sumOne=sumZero=index=0;
        for (short s : receivedSignal) {
            sumOne += Math.pow(s * Math.sin(2 * Math.PI * freqOne * index / Constant.SAMPLE_RATE)-avgOne,2);
            sumZero += Math.pow(s* Math.sin(2 * Math.PI * freqZero * index / Constant.SAMPLE_RATE)-avgZero,2);
            index++;
        }*/
        if (Math.abs(sumOne)-Math.abs(sumZero) > Constant.modulationThreshold) result= 1;
        else if (Math.abs(sumOne)-Math.abs(sumZero) <-Constant.modulationThreshold) result= 0;
        if(result!=-1)
            System.out.println(freqOne+" decode 1 bit at count "+count+" with " +result );
        return result;
    }
}
